package nl.vu.few.ibivu.biomodels.client.ui;

import nl.vu.few.ibivu.biomodels.client.event.MessageBroadcastType;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWeb;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWebClickHandler;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWebDataField;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWebDataFieldType;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWebEdge;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWebEdgeData;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWebEvent;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWebGroup;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWebNode;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWebNodeData;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWebPresetLayout;
import nl.vu.few.ibivu.biomodels.client.ui.cytoscapeweb.CytoscapeWebReadyHandler;
import nl.vu.few.ibivu.biomodels.client.ui.view.ModelView;
import nl.vu.few.ibivu.biomodels.shared.PetrifierDataFieldName;
import nl.vu.few.ibivu.biomodels.shared.PetrifierArcType;
import nl.vu.few.ibivu.biomodels.shared.PetrifierNodeType;
import nl.vu.few.ibivu.biomodels.shared.PetrifierModelData;

import com.google.gwt.json.client.JSONString;
import com.smartgwt.client.types.Cursor;
import com.smartgwt.client.types.VisibilityMode;
import com.smartgwt.client.widgets.layout.SectionStack;
import com.smartgwt.client.widgets.layout.SectionStackSection;

public class ModelViewImpl extends SectionStack implements ModelView {

	private CytoscapeWeb cytoscapeWeb;
	private CytoscapeWebNode sourceNode;

	private Presenter presenter;

	private ModelExecutionAnalysisImpl modelAnalysis;
	private ModelViewToolbox toolboxWindow;
	private ComponentDetailsWindow componentWindow;
	private ModelDetailsForm modelForm;

	public ModelViewImpl() {

		presenter = null;
		// Initialise the source node for drawing edges to null
		sourceNode = null;

		this.setWidth100();
		this.setHeight100();
		this.setVisibilityMode(VisibilityMode.MULTIPLE);
		
		/*
		 * Initialize CytoscapeWeb and his canvas
		 */
		cytoscapeWeb = CytoscapeWeb.create();
		componentWindow = new ComponentDetailsWindow();
		initCytoscapeWeb();
		
		SectionStackSection cytoscapeWebSection = new SectionStackSection("Model");
		cytoscapeWebSection.setCanCollapse(false);
		cytoscapeWebSection.setExpanded(true);
		cytoscapeWebSection.setShowHeader(false);
		cytoscapeWebSection.setCanReorder(false);
		cytoscapeWebSection.setResizeable(false);
		cytoscapeWebSection.addItem(cytoscapeWeb.getCytoscapeWebCanvas());
		this.addSection(cytoscapeWebSection);
		
		SectionStackSection resultSection = new SectionStackSection("Execution Analysis");
		resultSection.setCanCollapse(true);
		resultSection.setExpanded(false);
		resultSection.setCanReorder(false);
		resultSection.setResizeable(false);
		modelAnalysis = new ModelExecutionAnalysisImpl();
		resultSection.addItem(modelAnalysis);
		this.addSection(resultSection);
		
		/*
		 * Initialize the datails section
		 */
		SectionStackSection detailsSection = new SectionStackSection("Details");
		detailsSection.setCanCollapse(true);
		detailsSection.setExpanded(false);
		cytoscapeWebSection.setCanReorder(false);
		cytoscapeWebSection.setResizeable(false);
		modelForm = new ModelDetailsForm();
		detailsSection.addItem(modelForm);
		this.addSection(detailsSection);
		
		/*
		 * Initialize the toolbox window
		 */
		toolboxWindow = new ModelViewToolbox(cytoscapeWeb);
		//FIXME
		//toolboxWindow.setParentElement(cytoscapeWeb.getCytoscapeWebCanvas());
		toolboxWindow.setParentElement(this);
		toolboxWindow.setKeepInParentRect(true);
		toolboxWindow.setAutoShowParent(true);
		toolboxWindow.show();
	}

	private void initCytoscapeWeb() {
		// Initialize CytoscapeWeb callbacks for nodes and edges creation.
		// Create a new element node when you double click on the empty canvas
		cytoscapeWeb.onDoubleClick(new CytoscapeWebClickHandler() {

			@Override
			public void onClick(CytoscapeWebEvent event) {
				CytoscapeWebNode node = createNode(event.getMouseX(), event.getMouseY());
				if (toolboxWindow.getSelectedNodeElementType() == 
					PetrifierNodeType.COMPONENT) {
					componentWindow.setPresenter(new ComponentDetailsEditor(cytoscapeWeb, node));
					componentWindow.show();
				}
				if (isNodeSourceSelected() && !isNodeElementTypeEqual(sourceNode, node)) {
					createArc(sourceNode, node);
				}
				selectNodeSource(node);
			}

		}, CytoscapeWebGroup.none);

		// Create a new edge node when you double click on an edge node
		cytoscapeWeb.onDoubleClick(new CytoscapeWebClickHandler() {

			@Override
			public void onClick(CytoscapeWebEvent event) {
				CytoscapeWebNode node = (CytoscapeWebNode) event.getTarget();
				// Have you previously selected a source node?
				if (!isNodeSourceSelected()) {
					// No. The currently selected node will be the new source
					selectNodeSource(node);
				} else {
					// Yes. Let's check if the wanted edge is legal
					// Are the source node and the target node the same? No
					// loops!
					if (sourceNode.getData().getId().equals(node.getData().getId())) {
						// Yes.
						// Reset the edge creation
						deselectNodeSource();
					} else {
						// They are different nodes
						// Are the two nodes of the same node element type? Keep the graph
						// bipartite!
						if (isNodeElementTypeEqual(sourceNode, node)) {
							// Yes they are of the same kind
							presenter.broadcastMessage(MessageBroadcastType.ERROR, "You can't connect node of the same kind together.");
						} else {
							// No, they are of different kind
							// Does an edge in between the two nodes already
							// exist?
							if (cytoscapeWeb.getEdge(sourceNode, node) == null) {
								// No, we finally create the arc
								createArc(sourceNode, node);
								// We reset the source node
								deselectNodeSource();
							} else {
								// Yes, the arc already exists
								presenter.broadcastMessage(MessageBroadcastType.ERROR, "The arc that you are trying to add does already exist.");
							}
						}
					}
				}
			}

		}, CytoscapeWebGroup.nodes);

		cytoscapeWeb.onReady(new CytoscapeWebReadyHandler() {
			@Override
			public void onReady() {
				// Initialize DataScheme
				cytoscapeWeb.addDataField(CytoscapeWebGroup.nodes, CytoscapeWebDataField.create(
						PetrifierDataFieldName.NODE_ELEMENT_TYPE.name(), CytoscapeWebDataFieldType.STRING, new JSONString("COMPONENT")));
				cytoscapeWeb.addDataField(CytoscapeWebGroup.nodes, CytoscapeWebDataField.create(
						PetrifierDataFieldName.UNIPROT_ID.name(), CytoscapeWebDataFieldType.STRING));
				cytoscapeWeb.addDataField(CytoscapeWebGroup.nodes, CytoscapeWebDataField.create(
						PetrifierDataFieldName.label.name(), CytoscapeWebDataFieldType.STRING));
				cytoscapeWeb.addDataField(CytoscapeWebGroup.nodes, CytoscapeWebDataField.create(
						PetrifierDataFieldName.ARC_ELEMENT_TYPE.name(), CytoscapeWebDataFieldType.STRING, new JSONString("ACTIVATION")));
				cytoscapeWeb.addDataField(CytoscapeWebDataField.create(PetrifierDataFieldName.HIGHLIGHT.name(),
						CytoscapeWebDataFieldType.STRING));

				// The model has been drawn
				presenter.setDrawn();
			}
		});
	}

	/**
	 * Set the highlight of a node on or off.
	 * 
	 * @param node
	 *            The node to be highlighted
	 * @param value
	 *            {@code true} if you want to set the highlight on,
	 *            {@code false} otherwise
	 */
	protected void setHighlight(CytoscapeWebNode node, boolean value) {
		node.getData().put(PetrifierDataFieldName.HIGHLIGHT.name(), value ? new JSONString("RED") : new JSONString(""));
		cytoscapeWeb.updateNodeData(node);
	}

	protected CytoscapeWebNode createNode(double x, double y) {
		PetrifierNodeType type = toolboxWindow.getSelectedNodeElementType();
		CytoscapeWebNodeData data = new CytoscapeWebNodeData();
		data.put(PetrifierDataFieldName.NODE_ELEMENT_TYPE.name(), new JSONString(type.name()));
		presenter.invalidateModelData();
		return cytoscapeWeb.addNode(x, y, data, true);
	}

	protected CytoscapeWebEdge createArc(CytoscapeWebNode source, CytoscapeWebNode target) {
		PetrifierArcType type  = toolboxWindow.getSelectedArcElementType();
		CytoscapeWebEdgeData data = new CytoscapeWebEdgeData(source, target, true);
		data.put(PetrifierDataFieldName.ARC_ELEMENT_TYPE.name(), new JSONString(type.name()));
		presenter.invalidateModelData();
		return cytoscapeWeb.addEdge(data, true);
	}
	
	protected boolean isNodeElementTypeEqual(CytoscapeWebNode source, CytoscapeWebNode target) {
		PetrifierNodeType sourceType = PetrifierNodeType
		.valueOf(source.getData().get(PetrifierDataFieldName.NODE_ELEMENT_TYPE.name()).isString()
				.stringValue());
PetrifierNodeType targetType = PetrifierNodeType
		.valueOf(target.getData().get(PetrifierDataFieldName.NODE_ELEMENT_TYPE.name()).isString()
				.stringValue());
		return sourceType.equals(targetType);
	}
	
	protected boolean isNodeSourceSelected() {
		return (sourceNode != null);
	}
	
	protected void selectNodeSource(CytoscapeWebNode node) {
		if (sourceNode != null) {
			setHighlight(sourceNode, false);
		}
		sourceNode = node;
		if (sourceNode != null) {
			setHighlight(sourceNode, true);
		}
	}
	
	protected void deselectNodeSource() {
		if (sourceNode != null) {
			setHighlight(sourceNode, false);
			sourceNode = null;
		}
	}

	@Override
	protected void onDraw() {
		super.onDraw();

		if (presenter != null) {
			cytoscapeWeb.draw(presenter.getModelData().getNetworkModel(),
					presenter.getModelData().getLayout());
		}
	}

	@Override
	public void drawModel() {
		if (this.isDrawn()) {
			// Ask immediately for the model to be redrawn
			cytoscapeWeb.draw(presenter.getModelData().getNetworkModel(),
					presenter.getModelData().getLayout());
		}
		// Otherwise wait for the ModelView component to be drawn first
		// See onDraw()
	}
	
	@Override
	public void setPresenter(Presenter presenter) {
		this.presenter = presenter;
		toolboxWindow.setPresenter(presenter);
		modelForm.setPresenter(presenter);
		modelAnalysis.setPresenter(presenter);
	}

	@Override
	public void showLoader() {
		this.setCursor(Cursor.WAIT);
		this.disable();
	}

	@Override
	public void clearLoader() {
		this.setCursor(Cursor.DEFAULT);
		this.enable();
	}

	@Override
	public PetrifierModelData buildModelData() {
		deselectNodeSource();
		PetrifierModelData data = new PetrifierModelData();
		data.setLayout(CytoscapeWebPresetLayout.createFromNodes(true, cytoscapeWeb.getNodes()));
		data.setNetworkModel(cytoscapeWeb.getNetworkModel());
		return data;
	}

	@Override
	public String buildThumbnail() {
		return cytoscapeWeb.png();
	}

}
